﻿using Newtonsoft.Json.Serialization;
using Sustainalytics.AwsS3.Client;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.DataLayer.ConnectionStrings;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.Entities;
using Sustainalytics.Entities.ConnectionStrings;
using Sustainalytics.FileManager;
using Sustainalytics.GCCS;
using Sustainalytics.GCCS.CompanyReportSyncStep;
using Sustainalytics.Governance.Reporting;
using Sustainalytics.ReportGenerator.BL;
using Sustainalytics.Utils;
using Sustainalytics.Utils.Configuration;
using Sustainalytics.Utils.Serilog;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks.Dataflow;
using System.Web.Http;
using System.Web.Http.Cors;
using System.Web.Http.Dispatcher;

namespace Sustainalytics.PdfGeneration.Service.Controllers
{
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    [RoutePrefix("api/v1")]
    [LogControllerFilter(true, true, false)]
    public class CompanyReportController : ApiController
    {
        private readonly LogAggregator _logagg;
        private IS3Client _s3Client;
        private readonly int _bulkSize = 500;

        public CompanyReportController(LogAggregator logAggregator)
        {
            _logagg = logAggregator;
        }

        [Route("reportstatus")]
        public HttpResponseMessage Get()
        {
            return Request.CreateResponse(HttpStatusCode.OK, new { Startup.IsGenerationRunning });
        }

        [Route("cancelgeneration")]
        public HttpResponseMessage Post()
        {
            if (Startup.Token == null)
            {
                return Request.CreateResponse(HttpStatusCode.Accepted);
            }

            Startup.Token.Cancel(false);
            Startup.IsGenerationRunning = false;
            Startup.Token.Dispose();
            Startup.Token = new CancellationTokenSource();
            Console.WriteLine("Cancel pdf generation done");
            PdfGenerationSerilog.Log.CancelGeneration("Cancel pdf generation done");
            return Request.CreateResponse(HttpStatusCode.Accepted);
        }

        [Route("generate")]
        public HttpResponseMessage Post([FromBody] PdfRequest pdfRequest)
        {
            if (Startup.IsGenerationRunning)
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest, "There is a generation already running");
            }

            lock (Startup.LockerRunning)
            {
                var validation = this.ValidatePDFHttpRequestMessage(pdfRequest);

                if (!string.IsNullOrEmpty(validation))
                {
                    return Request.CreateResponse(HttpStatusCode.BadRequest, validation);
                }

                StartGeneration(pdfRequest);

                GenerateRiskRatingReport(pdfRequest);

                GenerateGovernanceReport(pdfRequest);

                GenerateGCCSReport(pdfRequest);

                var generationSuccess = GenerateTearSheetReports(pdfRequest.ConnectionStrings);

                MarkGenerationAsSuccessful(generationSuccess);

                return Request.CreateResponse(HttpStatusCode.OK);
            }
        }

        private void GenerateRiskRatingReport(PdfRequest pdfRequest)
        {
            var reportsManager = Sustainalytics.RiskRatings.Reporting.ReportsManager.GetInstance(100, pdfRequest.ConnectionStrings);
            reportsManager.GenerateBulkPdfReports();
            _logagg.LogSummary(string.Format("{0}: RiskRatingPDF - Companies to be process: {1}. Successfully generated {2}/{1} full reports and {3}/{4} summary reports",
                DateTime.UtcNow.ToString("T"),reportsManager.ReportsFullCount, reportsManager.SuccessfullyGeneratedFullReports, reportsManager.SuccessfullyGeneratedSummaryReports, reportsManager.ReportsSummaryCount));

            reportsManager.GenerateBulkPdfReports_Core();
            _logagg.LogSummary(string.Format("{0}: RiskRatingPDF - Core companies to be process: {1}. Successfully generated {2}/{1} full reports and {3}/{4} summary reports",
                DateTime.UtcNow.ToString("T"),reportsManager.ReportsCoreFullCount, reportsManager.SuccessfullyGenerateCoreFullReports, reportsManager.SuccessfullyGeneratedCoreSummaryReports, reportsManager.ReportsCoreSummaryCount));

            //Log generation errors if case
            reportsManager.Log.ToList().ForEach(msg => _logagg.Log(msg));
        }

        private void GenerateGovernanceReport(PdfRequest pdfRequest)
        {
            var databaseGroup = GetDatabaseGroup(pdfRequest);

            try
            {
                var reportsManager = GetReportManager(databaseGroup);

                reportsManager.GenerateBulkPdfReports(pdfRequest.CompanyIds);
                
                var log = reportsManager.Log;
                log.ToList().ForEach(msg => _logagg.Log(msg));

                _logagg.LogSummary(DateTime.UtcNow.ToString("T") + ":Generated Governance PDF's count: " + log.Count(a => a.Contains("company report was generated and saved successfully")));
            }
            catch (Exception ex)
            {
                CommonEventSerilog.Log.Exception(ex);
                throw new Exception(DateTime.UtcNow.ToString("T")+$":Failed to generate bulk company reports. Exception: " + ex.ToString());
            }
        }

        private void GenerateGCCSReport(PdfRequest pdfRequest)
        {
            if (!pdfRequest.GlobalCompactQuarterChanged)
            {
                _logagg.Log("No changes detected for global compact collections!");
                return;
            }

            string clientDwConnectionString;

            if (!pdfRequest.ConnectionStrings.TryGetValue("ClientDW", out clientDwConnectionString))
            {
                throw new ArgumentException("ClientDW connection string is missing from SyncContext!");
            }

            var maxDegreeOfParallelism = ParallelHelper.GetMaxDegreeOfParallelism();

            var bulkDataReader = new GCCSReportBulkDataReader(_bulkSize, clientDwConnectionString, _logagg, Startup.Token.Token, maxDegreeOfParallelism);

            var currentQuarterRepo = new RepositoryCollection<GCCurrentQuarter>(clientDwConnectionString);
            var corporateDataRep = new RepositoryCollection<CorporateData>(clientDwConnectionString);

            var globalCompactResearchStates = corporateDataRep.ReadCount(x => x.GccsResearchState.ResearchState == ResearchState.ResearchAvailable);

            _logagg.Log($"Corporate data Has GCCS Researched companies: {globalCompactResearchStates}");

            var imageFileManager = new DiskFileManager(@"Images\GcPdfExport");

            var gcExportManager = new GCExportManager(currentQuarterRepo, imageFileManager, null, null, null, null);

            var count = 0;

            Action<GccsReportData> action = reportData =>
            {
                if (reportData.OverallCompanySummary == null)
                {
                    _logagg.LogSummary(string.Format("Company profile with Id {0} has OverallCompanySummary == null", reportData.CompanyId));
                    return;
                }

                if (reportData.Principles.Count > 0)
                {
                    var sw = Stopwatch.StartNew();

                    var gccsPdfReportContent = gcExportManager.GeneratePdfReport(reportData.CurrentQuarter, reportData.Assessment, false,
                        reportData.OverallCompanySummary, reportData.Principles, reportData.PrincipleEventIndicators, reportData.Sources);

                    var report = new GccsReport
                    {
                        Id = Guid.NewGuid(),
                        CompanyId = reportData.CompanyId,
                        Assessment = reportData.Assessment,
                        FileContent = gccsPdfReportContent,
                        MimeType = "application/pdf"
                    };

                    var companyName = reportData.OverallCompanySummary.CompanyProfile.Name;

                    report.FileName = reportData.Assessment.GetFullReportName(companyName, reportData.CompanyId);

                    S3Write(report, _s3Client);
                    //SavePdfReport(report.FileContent, report.FileName);

                    sw.Stop();

                    _logagg.Log($"{companyName} has report generated succesfully for assesment: {reportData.Assessment}. Company ID: {reportData.CompanyId}. Elapsed time:{sw.Elapsed}");
                    Interlocked.Increment(ref count);
                }
                else
                {
                    _logagg.Log(
                        $"{reportData.OverallCompanySummary.CompanyProfile.Name} has NO report generated for assesment: {reportData.Assessment}. Company ID: {reportData.CompanyId}");
                }
            };

            var gccsReportDataConsumer = new ActionBlock<GccsReportData>(action, new ExecutionDataflowBlockOptions
            {
                BoundedCapacity = maxDegreeOfParallelism * 10,
                MaxDegreeOfParallelism = maxDegreeOfParallelism
            });

            var gccsReportDataProducer = new BufferBlock<GccsReportData>(new DataflowBlockOptions
            {
                BoundedCapacity = maxDegreeOfParallelism * 10
            });

            gccsReportDataProducer.LinkTo(gccsReportDataConsumer, new DataflowLinkOptions { PropagateCompletion = true });

            for (; ; )
            {
                try
                {
                    var batch = bulkDataReader.NextBulk(pdfRequest.CompanyIds.ToList());

                    if (!batch.Any())
                        break;

                    foreach (var reportData in batch)
                    {
                        while (!gccsReportDataProducer.Post(reportData))
                        {
                            Thread.Sleep(1000);
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception($"Failed to generate bulk GCCS reports. Exception: " + ex.ToString());
                }
            }


            gccsReportDataProducer.Complete();
            gccsReportDataConsumer.Completion.Wait();

            _logagg.LogSummary("Generated GCCS PDF's count: " + count);
        }

        private void StartGeneration(PdfRequest pdfRequest)
        {
            Startup.IsGenerationRunning = true;
            Startup.Token = new CancellationTokenSource();

            var pdfGenerator = new PdfReportGeneration(
                _logagg.Log,
                pdfRequest.ConnectionStrings,
                Startup.Token.Token,
                pdfRequest.AffectedControversiesCompanies,
                pdfRequest.CompanyIds);
         
            var config = new HttpConfiguration();

            config.Filters.Add(new LogControllerFilter(true, true, false));

            var container = CompositionRoot.RegisterDependencies(config, pdfRequest.ConnectionStrings);

            config.Services.Replace(typeof(IHttpControllerActivator), new SimpleInjectorControllerActivator(container));
          
            _s3Client = container.GetInstance<IS3Client>();

            pdfGenerator.Start(container);
        }

        private bool GenerateTearSheetReports(Dictionary<string, string> connectionStrings)
        {
            var connectionStringKeys = new string[] { "ClientPlatform", "ClientUniverses", "ClientDW" };
            var missingConnectionStrings = connectionStringKeys.Where(key => !connectionStrings.ContainsKey(key)).ToList();

            if (missingConnectionStrings != null && missingConnectionStrings.Any())
            {
                _logagg.LogSummary(string.Format("Failed to generate TearSheetPDF Reports because following connection strings are missing: {0}",
                    string.Join("; ", missingConnectionStrings)));
                return false;
            }

            if (ConfigurationManager.AppSettings["TearSheetAccountNames"] == null)
            {
                _logagg.LogSummary("Failed to generate TearSheetPDF Reports because no TearSheetAccountNames AppSettings key was found in AppConfig");
                return false;
            }

            var databaseGroupLetter = DatabaseGroupLetter(connectionStrings["ClientDW"]);

            //Delete all items from the S3 Bucket before reports generation
            EmptyTearSheetFolder(databaseGroupLetter);

            //Generate TearSheet pdf files into S3
            var accountNames = new[] { ConfigurationManager.AppSettings["TearSheetAccountNames"] };
            var reportsManager = TearSheetReportsManager.GetInstance(100, connectionStrings, accountNames, _s3Client);
            reportsManager.GenerateBulkPdfReports();

            //Log generation errors if case
            reportsManager.Log.ToList().ForEach(msg => _logagg.Log(msg));

            _logagg.LogSummary(string.Format("TearSheetPDF - Reports to be generated {0}. Successfully generated {1}",
                reportsManager.ReportsCount, reportsManager.SuccessfullyGeneratedReports));

            //Copy TearSheet S3 reports to public folder
            var result = ReplicateS3TearSheetReportsFolder(databaseGroupLetter);
            //result.
            return result;
        }

        private string DatabaseGroupLetter(string connectionString)
        {
            if (connectionString.Contains("ClientDW_A"))
                return "A";
            if (connectionString.Contains("ClientDW_B"))
                return "B";
            return string.Empty;
        }

        private void EmptyTearSheetFolder(string databaseGroupLetter)
        {
   
            var bucketName = ConfigurationManager.AppSettings["s3bucket"];
            if (string.IsNullOrEmpty(bucketName))
            {
                _logagg.LogSummary("Failed to empty TearSheet folder before reports generation because no s3bucket key was found in AppConfig or invalid value.");
                return;
            }

            try
            {
                var prefix = string.Format("{0}_{1}", ReportType.TearSheetReport.ToString(), databaseGroupLetter);
                _s3Client.DeleteBulk(bucketName, prefix);

            }
            catch (Exception ex)
            {
                _logagg.LogSummary($"Failed to empty TearSheet folder before reports generation. Exception message: {ex.Message}");
            }
        }

        private bool ReplicateS3TearSheetReportsFolder(string databaseGroupLetter)
        {
  
            var sourceBucket = ConfigurationManager.AppSettings["s3bucket"];
            if (string.IsNullOrEmpty(sourceBucket))
            {
                _logagg.LogSummary("Failed to copy S3 TearSheetPDFReports folder because no s3bucket key was found in AppConfig or invalid value.");
                return false;
            }

            var destinationBucket = ConfigurationManager.AppSettings["TearSheetPublicBucket"];
            if (string.IsNullOrEmpty(destinationBucket))
            {
                _logagg.LogSummary("Failed to copy S3 TearSheetPDFReports folder because no TearSheetPublicBucket key was found in AppConfig or invalid value.");
                return false;
            }

            try
            {
              
                {
                    return ReplicateS3TearSheetReportsFolder(_s3Client, databaseGroupLetter, sourceBucket, destinationBucket);
                }
            }
            catch (Exception ex)
            {
                _logagg.LogSummary($"Failed to copy S3 TearSheetPDF Reports folder to public bucket. Exception message: {ex.Message}");
                return false;
            }
        }

        private bool ReplicateS3TearSheetReportsFolder(IS3Client s3Client, string databaseGroupLetter, string sourceBucket, string destinationBucket)
        {
            var sourcePrefix = string.Format("{0}_{1}", ReportType.TearSheetReport.ToString(), databaseGroupLetter);
            var destinationPrefix = string.Empty;

            var copyFilesFailed = 0;
            var sourceFileKeys = s3Client.GetS3Objects(sourceBucket, sourcePrefix).Select(x => x.Key);
            foreach (var fileKey in sourceFileKeys)
            {
                try
                {
                    s3Client.CopyFile(sourceBucket, fileKey, destinationBucket, ReplaceKeyPrefix(fileKey, destinationPrefix));
                }
                catch (Exception ex)
                {
                    copyFilesFailed++;
                    _logagg.Log(ex.Message);
                }
            }

            var deleteFilesFailed = 0;
            var destinationFileNames = s3Client.GetS3Objects(destinationBucket, destinationPrefix)
                .Select(x => GetFileNameFromKey(x.Key))
                .Except(sourceFileKeys.Select(GetFileNameFromKey));
            foreach (var fileName in destinationFileNames)
            {
                try
                {
                    s3Client.DeleteFile(destinationBucket, fileName);
                }
                catch (Exception ex)
                {
                    deleteFilesFailed++;
                    _logagg.Log(ex.Message);
                }
            }

            if (copyFilesFailed != 0 || deleteFilesFailed != 0)
            {
                _logagg.LogSummary($"Failed to copy {copyFilesFailed} S3 TearSheetPDF files from source bucket {sourceBucket} to public bucket folder {destinationBucket}");
                _logagg.LogSummary($"Failed to delete {deleteFilesFailed} TearSheetPDF files from public bucket {destinationBucket}");
                return false;
            }

            _logagg.LogSummary($"Successfully copied {sourceFileKeys.Count()} TearSheetPDF files from source bucket {sourceBucket} to destination bucket {destinationBucket}");
            _logagg.LogSummary($"Successfully deleted {destinationFileNames.Count()} TearSheetPDF files from destination bucket {destinationBucket}");
            return true;
        }

        private string ReplaceKeyPrefix(string key, string newPrefix)
        {
            var fileName = GetFileNameFromKey(key);
            return $"{newPrefix}/{fileName}";
        }

        private string GetFileNameFromKey(string key)
        {
            var items = key.Split('/');
            if (items.Length > 1)
                return items[items.Length - 1];
            return key;
        }

        private void MarkGenerationAsSuccessful(bool generationSuccess)
        {
            if (generationSuccess)
                _logagg.LogSuccess("Successful");
            else
                _logagg.LogError("");

            Startup.IsGenerationRunning = false;
            Startup.Token.Dispose();
            Startup.Token = new CancellationTokenSource();
            TTLManager.Shutdown();
        }

        private string ValidatePDFHttpRequestMessage(PdfRequest pdfRequest)
        {
            string loggError = string.Empty;

            if (pdfRequest == null)
            {
                loggError = string.Format(
                    "For the request: {0} .The body of the request is null",
                    Request.Content.ReadAsStringAsync().Result);
                PdfGenerationSerilog.Log.LogNullPdfRequest(loggError);
            }

            return loggError;
        }

        private void S3Write(IFileContent report, IS3Client s3Client)
        {
            var bucketName = SettingsHelper.Read("s3bucket", "dev-globalaccess-europe");

            var path = $"{ReportType.GlobalCompactComplianceReport}/{report.FileName}";

            _logagg.Log(DateTime.UtcNow.ToString("T")+$":Uploading file {path} in S3 {bucketName} bucket");

            s3Client.UploadFile(bucketName, path, report.FileContent);
        }

        private void SavePdfReport(byte[] file, string fileName)
        {
            if (!Directory.Exists(@"E:\Reports\"))
                Directory.CreateDirectory(@"E:\Reports\");

            File.WriteAllBytes(string.Format(@"E:\Reports\{0}", @fileName), file);
        }

        private static string GetDatabaseGroup(PdfRequest pdfRequest)
        {
            if (pdfRequest.ConnectionStrings == null || pdfRequest.ConnectionStrings.Count == 0)
                throw new ArgumentException(
                    "Connection strings must be provided in order to create repositories!");

            string connectionString;

            if (!pdfRequest.ConnectionStrings.TryGetValue("GovernanceDW", out connectionString))
            {
                throw new ArgumentException("No connection string found for GovernanceDW!");
            }

            string databaseGroup = null;

            if (connectionString.Contains("GovernanceDW_A"))
                databaseGroup = "A";

            if (connectionString.Contains("GovernanceDW_B"))
                databaseGroup = "B";

            return databaseGroup;
        }

        private IReportsManager GetReportManager(string databaseGroup)
        {
            string inputFilesConnectionString = ConfigurationManager.ConnectionStrings["GovernanceInputFile"].ConnectionString;
            var dwConnectionString = GetConnectionString(ConnectionStringType.GovernanceDW);

            if (dwConnectionString.Contains("GovernanceDW_A"))
                dwConnectionString = dwConnectionString.Replace("GovernanceDW_A", $"GovernanceDW_{databaseGroup}");
            if (dwConnectionString.Contains("GovernanceDW_B"))
                dwConnectionString = dwConnectionString.Replace("GovernanceDW_B", $"GovernanceDW_{databaseGroup}");

            var connectionStrings = new Dictionary<string, string>
            {
                {"GovernanceDW", dwConnectionString},
                {"GovernanceInputFile", inputFilesConnectionString}
            };

            var reportsManager = ReportsManager.GetInstance(100, connectionStrings, databaseGroup);

            return reportsManager;
        }

        private string GetConnectionString(ConnectionStringType connectionStringType)
        {
            var connectionStringsManager =
                ConnectionStringsManager.GetInstance(
                    ConnectionStringsRepository.GetInstance(ConfigurationManager.ConnectionStrings["ClientPlatform"].ConnectionString));

            return connectionStringsManager.GetConnectionString(connectionStringType);
        }
    }
}
